【アップデート】CodeGuru ProfilerがPythonアプリ向けのレコメンデーションをサポートするようになりました
CX事業本部@大阪の岩田です。CodeGuru Profilerのレコメンデーションは元々Javaのみサポートされていましたが、本日のアップデートでCodeGuru ProfilerがPythonアプリ向けのレコメンデーションをサポートするようになりました。
すでにCodeGuru Profilerを利用している場合は、特に設定変更やコード修正無しでそのままRecomendationが利用できます。実際にマネコンから確認してみたところ、本リリース以前である7月末に収集したプロファイル情報に対してレコメンデーションレポートが作成されていました。
やってみる
実際にレコメンデーションを試してみましょう。今回はLambdaの実装でやってしまいがちなboto3のresource/clientを再利用せずに都度生成する実装をチェックしてみます。
ソースコードの準備とデプロイ
試しにServerless FrameworkのExampleからAPI GW × Lambda × DynamoDBを利用した簡単なREST APIを作成するアプリaws-python-rest-api-with-dynamodbを微修正したアプリをデプロイして確認してみます。
まずソースコードを一部改変し、Lambdaのhandler内でboto3にresourceクラスを生成するように修正します
$ git diff aws-python-rest-api-with-dynamodb/todos/list.py diff --git a/aws-python-rest-api-with-dynamodb/todos/list.py b/aws-python-rest-api-with-dynamodb/todos/list.py index b133210..11ef307 100644 --- a/aws-python-rest-api-with-dynamodb/todos/list.py +++ b/aws-python-rest-api-with-dynamodb/todos/list.py @@ -3,10 +3,11 @@ import os from todos import decimalencoder import boto3 -dynamodb = boto3.resource('dynamodb') +# dynamodb = boto3.resource('dynamodb') def list(event, context): + dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # fetch all todos from the database
ソースコード全体は以下のようになります
import json import os from todos import decimalencoder import boto3 # dynamodb = boto3.resource('dynamodb') def list(event, context): dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # fetch all todos from the database result = table.scan() # create a response response = { "statusCode": 200, "body": json.dumps(result['Items'], cls=decimalencoder.DecimalEncoder) } return response
続いてserverless.yml
を修正し、CodeGuru Profilerを利用するための設定を追加します。デプロイを簡略化するために、AWSが提供しているLambda Layersを利用する方法を採用しています
$ git diff aws-python-rest-api-with-dynamodb/serverless.yml diff --git a/aws-python-rest-api-with-dynamodb/serverless.yml b/aws-python-rest-api-with-dynamodb/serverless.yml index fac75da..27e6b86 100644 --- a/aws-python-rest-api-with-dynamodb/serverless.yml +++ b/aws-python-rest-api-with-dynamodb/serverless.yml @@ -7,6 +7,11 @@ provider: runtime: python3.8 environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} + AWS_CODEGURU_PROFILER_GROUP_NAME: !Ref ProfilingGroup + AWS_LAMBDA_EXEC_WRAPPER: /opt/codeguru_profiler_lambda_exec + layers: + - arn:aws:lambda:ap-northeast-1:157417159150:layer:AWSCodeGuruProfilerPythonAgentLambdaLayer:10 + region: ap-northeast-1 iamRoleStatements: - Effect: Allow Action: @@ -17,7 +22,11 @@ provider: - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" - + - Effect: Allow + Action: + - codeguru-profiler:ConfigureAgent + - codeguru-profiler:PostAgentProfile + Resource: "*" functions: create: handler: todos/create.create @@ -77,3 +86,11 @@ resources: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} + ProfilingGroup: + Type: AWS::CodeGuruProfiler::ProfilingGroup + Properties: + ProfilingGroupName: MyProfilingGroup + ComputePlatform: AWSLambda + AgentPermissions: + Principals: + - !GetAtt IamRoleLambdaExecution.Arn \ No newline at end of file
serverless.yml
の全体です
service: serverless-rest-api-with-dynamodb frameworkVersion: ">=1.1.0 <=2.1.1" provider: name: aws runtime: python3.8 environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} AWS_CODEGURU_PROFILER_GROUP_NAME: !Ref ProfilingGroup AWS_LAMBDA_EXEC_WRAPPER: /opt/codeguru_profiler_lambda_exec layers: - arn:aws:lambda:ap-northeast-1:157417159150:layer:AWSCodeGuruProfilerPythonAgentLambdaLayer:10 region: ap-northeast-1 iamRoleStatements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" - Effect: Allow Action: - codeguru-profiler:ConfigureAgent - codeguru-profiler:PostAgentProfile Resource: "*" functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{id} method: get cors: true update: handler: todos/update.update events: - http: path: todos/{id} method: put cors: true delete: handler: todos/delete.delete events: - http: path: todos/{id} method: delete cors: true resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ProfilingGroup: Type: AWS::CodeGuruProfiler::ProfilingGroup Properties: ProfilingGroupName: MyProfilingGroup ComputePlatform: AWSLambda AgentPermissions: Principals: - !GetAtt IamRoleLambdaExecution.Arn
準備できたらデプロイしましょう
$sls deploy
プロファイル情報の収集
デプロイできたらプロファイル情報を収集するために、しばらくTODO一覧取得のAPIを実行します。
while true; do curl https://<API GWのエンドポイント>/todos; sleep 10; done
プロファイル結果とどの程度関連するかは分かりませんが、初期状態だとTODO一覧取得のレスポンスが空になるので、事前にTODO作成のAPIを何度か実行して適当にデータを作成してから実行しました。
レコメンデーションレポートの確認
しばらく待ったらレコメンデーションレポートの確認へ...進みたかったのですが、レポートが表示されるまでに時間がかかるので、7月末に収集していたプロファイル情報に対するレポートを確認してみました。7月末時点ではPythonアプリに対するレコメンデーションレポートがサポートされていませんでしたが、後付でレポートが生成されているところが素晴らしいですね!
期待通りboto3のresource/clientを再利用していない点がレコメンデーションとして指摘してくれました!これでLambdaのパフォーマンスを改善しつつ、コスト削減も図れそうです。
まとめ
以前Lambdaのプロファイルにかんする記事を書いたのですが、プロファイル情報の取得/分析はそれなりに面倒な作業でした。CodeGuru Profilerを利用すると、諸々の面倒な作業を全て自動でやってくれる上に推奨事項まで提示してくれて非常に便利です。うまく利用してパフォーマンス問題の事前防止やLambdaの実行時間削減によるコスト最適化に活用していきたいですね